/**
 * Copyright (c) 2009 Blue Whale Systems Ltd
 * 
 * This software is provided "as is," and the copyright holder makes no representations or warranties, express or
 * implied, including but not limited to warranties of merchantability or fitness for any particular purpose or that the
 * use of this software or documentation will not infringe any third party patents, copyrights, trademarks or other
 * rights.
 * 
 * The copyright holder will not be liable for any direct, indirect special or consequential damages arising out of any
 * use of this software or documentation.
 */
package com.bluewhalesystems.client.lwuit.richtext;

/**
 * Provides a way to accumulate char content while minimizing the number of copy operations.
 */
public class GrowableCharArray
{
    /**
     * Creates an instance with default capacity and capacity increment values.
     */
    public GrowableCharArray()
    {
        this( DEFAULT_INITIAL_CAPACITY, DEFAULT_CAPACITY_INCREMENT );
    }

    /**
     * Creates an instance with given capacity and default capacity increment values.
     * 
     * @param aCapacity the initial capacity value
     */
    public GrowableCharArray( int aCapacity )
    {
        this( aCapacity, DEFAULT_CAPACITY_INCREMENT );
    }

    /**
     * Creates an instance with given capacity and capacity increment values.
     * 
     * @param aCapacity the initial capacity value
     * @param aCapacityIncrement the capacity increment value
     */
    public GrowableCharArray( int aCapacity, int aCapacityIncrement )
    {
        iCapacityIncrement = MIN_CAPACITY_INCREMENT > aCapacityIncrement ? MIN_CAPACITY_INCREMENT : aCapacityIncrement;
        iArray = new char[MIN_INITIAL_CAPACITY > aCapacity ? MIN_INITIAL_CAPACITY : aCapacity];
        iCount = 0;
    }

    /**
     * Appends a single character to to this object.
     * 
     * @param aValue the character to append
     */
    public void append( char aValue )
    {
        internalEnsureCapacity( iCount + 1, false );
        iArray[iCount++] = aValue;
    }

    /**
     * Appends the characters of the String to this object.
     * 
     * @param aString content to append
     */
    public void append( String aString )
    {
        append( aString.toCharArray(), 0, aString.length() );
    }

    /**
     * Appends the contents of the GrowableCharArray to this object. 
     * 
     * @param aGrowableString the content to append
     */
    public void append( GrowableCharArray aGrowableString )
    {
        append( aGrowableString.getArray(), 0, aGrowableString.length() );
    }

    /**
     * Appends the characters to this object.
     * 
     * @param aChars characters to append
     */
    public void append( char[] aChars )
    {
        append( aChars, 0, aChars.length );
    }

    /**
     * Appends a range of characters from the source array.
     * 
     * @param aChars source array
     * @param aOffset starting offset in the source array
     * @param aLength number of characters to copy
     */
    public void append( char[] aChars, int aOffset, int aLength )
    {
        if( 0 < aLength )
        {
            internalEnsureCapacity( iCount + aLength, false );
            System.arraycopy( aChars, aOffset, iArray, iCount, aLength );
            iCount += aLength;
        }
    }

    /**
     * Removes the last 'n' characters.
     * 
     * @param aCount number of characters to remove from the end of the array
     */
    public void remove( int aCount )
    {
        iCount -= aCount;

        if( 0 > iCount )
        {
            iCount = 0;
        }
    }

    /**
     * Gets a single character at index.
     * 
     * @param index index of character to retrieve
     * @return the character
     */
    public char get( int index )
    {
        return iArray[index];
    }

    /**
     * Gets the entire char[]. If the intent is to use the array on its own, call trim() first.
     * 
     * @return entire content
     */
    public char[] getArray()
    {
        return iArray;
    }

    /**
     * Returns a copy of the contents as a new String object.
     * 
     * @return entire content
     */
    public String getString()
    {
        return new String( iArray, 0, iCount );
    }

    /**
     * Gets the number of characters contained.
     * 
     * @return the content length
     */
    public int length()
    {
        return iCount;
    }

    /**
     * Gets the current capacity value. This minus content length gives the number of characters that can be added
     * without incurring the cost of a copy operation.
     * 
     * @return the content capacity
     */
    public int capacity()
    {
        return iArray.length;
    }

    /**
     * Increases, if necessary, the total capacity of this object.
     * 
     * @param aCapacity new total capacity
     */
    public void ensureCapacity( int aCapacity )
    {
        internalEnsureCapacity( aCapacity, false );
    }

    /**
     * Removes all the string content.
     */
    public void clear()
    {
        internalClear( false );
    }

    /**
     * Decreases, if necessary, the total capacity to match the actual content length.
     * 
     * @return the resulting trimmed array
     */
    public char[] trim()
    {
        if( iArray.length > iCount )
        {
            char[] newArray = new char[iCount];
            System.arraycopy( iArray, 0, newArray, 0, iCount );
            iArray = null;
            iArray = newArray;
        }

        return iArray;
    }

    private void internalEnsureCapacity( int aCapacity, boolean aForceCapacity )
    {
        int newCapacity;
        char[] newArray;

        if( aForceCapacity )
        {
            // Use the new capacity unless it would cause data loss.
            newCapacity = aCapacity >= iCount ? aCapacity : iCount;
        }
        else
        {
            if( iArray.length >= aCapacity )
            {
                // Short-circuit. This is going to be the default case for most add/insert operations.
                return;
            }

            // Increase capacity by at least the current capacity increment value.
            newCapacity = aCapacity - iArray.length > iCapacityIncrement ? aCapacity : iArray.length + iCapacityIncrement;
        }

        newArray = new char[newCapacity];
        System.arraycopy( iArray, 0, newArray, 0, iCount );
        iArray = newArray;
    }

    private void internalClear( boolean aForceClearArray )
    {
        iCount = 0;

        if( aForceClearArray )
        {
            // Do not leave as null because it would unnecessarily complicate logic in internal functionality.
            iArray = new char[0];
        }
    }

    void reset( int aCapacity, int aCapacityIncrement )
    {
        iCapacityIncrement = aCapacityIncrement;
        iArray = new char[aCapacity];
        iCount = 0;
    }

    private static final int DEFAULT_INITIAL_CAPACITY   = 1024;
    private static final int DEFAULT_CAPACITY_INCREMENT = 512;
    private static final int MIN_INITIAL_CAPACITY       = 128;
    private static final int MIN_CAPACITY_INCREMENT     = 64;

    private int              iCapacityIncrement;
    private int              iCount;
    private char[]           iArray;
}
